package com.yunfinal.api.service;

import com.alibaba.fastjson.JSONObject;
import com.jfinal.kit.StrKit;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * API式交互
 * 参数校验小工具
 * 杜福忠 2019-02-18
 */
public class ApiCheck {

    public static final ApiCheck ME = new ApiCheck();

    /**
     * 字符串为 null 或者内部字符全部为 ' ' '\t' '\n' '\r' 这四类字符时返回 true
     */
    public boolean isBlank(Object obj) {
        if (obj == null) {
            return true;
        }
        if (!(obj instanceof String)) {
            // 不是 String
            return false;
        }
        return StrKit.isBlank(obj.toString());
    }

    /**
     * 全链路调用记录 - 万里追踪查看代码调用情况
     */
    public void track() {
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        StringBuilder msg = new StringBuilder(64 * stackTrace.length);
        for (StackTraceElement s : stackTrace) {
            msg.append(s.getClassName());
            msg.append(".(").append(s.getFileName());
            msg.append(":");
            msg.append(s.getLineNumber());
            msg.append(")\n");
        }
        System.out.println(msg.toString());
    }

    /**
     * 获取 并 检验 KV 中 参数是否存在，不存在直接返回 400错误 ， 并附带必填参数名
     *
     * @param kv  集合
     * @param key 必填键
     * @return 获取到的值
     */
    public <T> T get(JSONObject kv, String key) {
        Object ret = kv.get(key);
        if (null != ret) {
            return (T) ret;
        }
        throw new ApiException("400", "缺失必填参数", key);
    }

    /**
     * 检验 KV 中 参数是否存在，不存在直接返回 400错误 ， 并附带必填参数名
     *
     * @param kv   集合
     * @param keys 必填键
     */
    public void containsKey(JSONObject kv, String... keys) {
        List<String> ret = new ArrayList<>(keys.length);
        for (String key : keys) {
            if (!kv.containsKey(key)) {
                ret.add(key);
            }
        }
        if (!ret.isEmpty()) {
            throw new ApiException("400", "缺失必填参数", ret);
        }
    }

    /**
     * 获取集合中 的 数字， 并且校验 是否在范围中， 如果不在则直接返回401 错误， 并附带范围和键
     *
     * @param kv  集合
     * @param key 键
     * @param min 最小
     * @param max 最大
     * @return 集合中的数字
     */
    public int intValue(JSONObject kv, String key, int min, int max) {
        int temp = kv.getIntValue(key);
        if (temp < min || temp > max) {
            throw new ApiException("401", "参数范围错误min:" + min + " max" + max,
                    key);
        }
        return temp;
    }

    /**
     * 获取集合中 的 数字， 并且校验 是否在范围中， 如果不在则直接返回401 错误， 并附带范围和键
     *
     * @param kv  集合
     * @param key 键
     * @param min 最小
     * @param max 最大
     * @return 集合中的数字
     */
    public long longValue(JSONObject kv, String key, long min, long max) {
        long temp = kv.getLongValue(key);
        if (temp < min || temp > max) {
            throw new ApiException("401", "参数范围错误min:" + min + " max" + max,
                    key);
        }
        return temp;
    }

    /**
     * 获取集合中 的 数字， 并且校验 是否在范围中， 如果不在则直接返回401 错误， 并附带范围和键
     *
     * @param kv  集合
     * @param key 键
     * @param min 最小
     * @param max 最大
     * @return 集合中的数字
     */
    public double doubleValue(JSONObject kv, String key, double min,
                              double max) {
        double temp = kv.getDoubleValue(key);
        if (temp < min || temp > max) {
            throw new ApiException("401", "参数范围错误min:" + min + " max" + max,
                    key);
        }
        return temp;
    }

    /**
     * 获取集合中 的 日期， 并且校验 是否在范围中， 如果不在则直接返回401 错误， 并附带范围和键
     *
     * @param kv  集合
     * @param key 键
     * @param min 最小
     * @param max 最大
     * @return 集合中的日期
     */
    public Date dateValue(JSONObject kv, String key, Date min, Date max) {
        Date temp = kv.getDate(key);
        if (temp.before(min) || temp.after(max)) {
            throw new ApiException("401", "参数范围错误min:" + min + " max" + max,
                    key);
        }
        return temp;
    }

    /**
     * 判断 集合 中 两个 值 是否相等， 如果相等 直接返回 401 错误， 附带 参数名
     *
     * @param kv   集合
     * @param key1
     * @param key2
     */
    public void equalsValue(JSONObject kv, String key1, String key2) {
        Object value_1 = kv.get(key1);
        Object value_2 = kv.get(key2);
        if (value_1 == null || value_2 == null || (!value_1.equals(value_2))) {
            throw new ApiException("401", "参数错误值不能相等",
                    new String[]{key1, key2});
        }
    }

    /**
     * 取email ， 如果格式不正确 直接返回 401 错误， 并附带参数名
     *
     * @param kv
     * @param key
     * @return
     */
    public String emailValue(JSONObject kv, String key) {
        String re = "\\b(^['_A-Za-z0-9-]+(\\.['_A-Za-z0-9-]+)*@([A-Za-z0-9-])+(\\.[A-Za-z0-9-]+)*((\\.[A-Za-z0-9]{2,})|(\\.[A-Za-z0-9]{2,}\\.[A-Za-z0-9]{2,}))$)\\b";
        return regex(kv, key, re, false, "参数email不正确");
    }

    /**
     * 匹配正则 ， 如果不匹配， 直接返回 401 错误， 并附带 错误信息
     *
     * @param kv              集合
     * @param key             键
     * @param regExpression   正则表达式
     * @param isCaseSensitive 是否区分大小写， （true 区分， false不区分）
     * @param errorMessage    返回的错误提示
     * @return 集合中的值
     */
    public String regex(JSONObject kv, String key, String regExpression,
                        boolean isCaseSensitive, String errorMessage) {
        String value = get(kv, key);
        Pattern pattern = isCaseSensitive
                ? Pattern.compile(regExpression)
                : Pattern.compile(regExpression, Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(value);
        if (!matcher.matches()) {
            throw new ApiException("401", errorMessage, key);
        }
        return value;
    }

}
